﻿#ifndef JSON_VISITOR_HELPER_H
#define JSON_VISITOR_HELPER_H
#pragma once

#include <tuple>
#include <vector>
#include <regex>

#include "estree.hpp"
#include "json.hpp"
#include "ast_visitor.hpp"
#include "macros.h"
#include "cyclone_type.hpp"

namespace antlr4
{
    class ParserRuleContext;
}
namespace antlr4::tree
{
    class ParseTree;
    class TerminalNode;
    class ErrorNode;
}
namespace cyclone::parser
{
    using antlr4::ParserRuleContext;
    using antlr4::tree::ErrorNode;
    using antlr4::tree::ParseTree;
    using antlr4::tree::TerminalNode;
    using Json = nlohmann::json;
    using JsonPtr = std::shared_ptr<nlohmann::json>;
    using JsonArray = std::vector<Json>;
    class DummyNode;
    DECLARE_STATIC_STRING_WITH_DEFAULT(start);
    DECLARE_STATIC_STRING_WITH_DEFAULT(end);
    NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Node, type, start, end);
    NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Terminal, literal, start, end);
    NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, typeId);

    template <typename _Value>
    struct KeyValue
    {
        std::string key;
        _Value value;
    };
    KeyValue(const char *, const char *)->KeyValue<std::string>;
    class JsonVisitorHelper
    {
        CHECK_TYPE_HAS_VISIBLE_PROPERTY_NAME(type);

    public:
        template <typename T>
        static typename std::enable_if_t<!Checktype<T>::value, Json>
        jsonifyTypeNode(T *node)
        {
            return Json::object();
        }

        template <typename T>
        static typename std::enable_if_t<Checktype<T>::value, Json>
        jsonifyTypeNode(T *node)
        {
            return Type{node->type};
        }

        static auto jsonifyTerminalNode(TerminalNode *node)
        {
            if (const auto &token = node->getSymbol())
            {
                Terminal jsonifyTerminal = {token->getStartIndex(), token->getStopIndex(), token->getText()};
                return Json(jsonifyTerminal);
            }
            return Json();
        }

        static auto formatManualNode(Json &output)
        {
            return output;
        }

        template <typename... Tys>
        static auto formatManualNode(Json &output, ParserRuleContext *rule, Tys... _rest)
        {
            auto pos = Node::findRuleStartAndEnd(rule);
            output[start] = pos.start;
            output[end] = pos.end;
            return formatManualNode(output, _rest...);
        }

        template <typename _Value, typename... Tys>
        static auto formatManualNode(Json &output, const KeyValue<_Value> &keyValue, Tys... _rest)
        {
            output[keyValue.key] = keyValue.value;
            return formatManualNode(output, _rest...);
        }
        template <typename T>
        std::enable_if_t<std::is_base_of_v<Node, T>, Json> static jsonifyRuleNode(const T &node)
        {
            return Json(node);
        }

        /**
         * NOTE The start index of the program is always 0 , antlr will ignore the leading space of the program however
         */
        static Json jsonifyRuleNode(FormalParser::ProgramContext *node)
        {
            const auto position = Node::findRuleStartAndEnd(node);
            return Node{0, position.end, node->type};
        }

        template <typename T>
        static typename std::enable_if_t<!Checktype<T>::value, Json>
        jsonifyRuleNode(T *node)
        {
            return Json();
        }

        template <typename T>
        static typename std::enable_if_t<Checktype<T>::value, Json>
        jsonifyRuleNode(T *node)
        {
            const auto position = Node::findRuleStartAndEnd(node);
            return Node{
                position.start,
                position.end,
                node->type,
            };
        }

        static Json jsonifyRuleNode(DummyNode *node);

        static auto simplifyJsonArrary(const JsonArray &array)
        {
            if (!array.size())
                return Json();
            return 1 == array.size() ? Json(array[0]) : Json(array);
        };
        static auto removeQuoteFromString(const std::string &str)
        {
            if (str.size() < 2)
                return str;
            return str.substr(1, str.size() - 2);
        }
    };

} // namespace cyclone::parser

#endif
